# Add to utils.py

def compute_metrics(model, test_loader, popularity_dict, device):
    """Compute all evaluation metrics"""
    model.eval()
    all_preds = []
    all_labels = []
    item_exposures = defaultdict(int)
    
    with torch.no_grad():
        for batch in test_loader:
            users = batch['user'].to(device)
            items = batch['item'].to(device)
            labels = batch['rating'].to(device)
            
            preds = model.scm(users, items)
            all_preds.extend(preds.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())
            
            # Track exposures
            _, topk = torch.topk(preds, k=10)
            for row in topk:
                for item in row:
                    item_exposures[item.item()] += 1

    # Calculate metrics
    ndcg = ndcg_score(all_labels, all_preds, k=10)
    recall = recall_score(all_labels, all_preds, k=10)
    coverage = coverage_score(item_exposures, len(popularity_dict))
    efi = exposure_fairness(item_exposures)
    gini_diff = gini_difference(item_exposures, popularity_dict)
    
    return {
        'ndcg@10': ndcg,
        'recall@10': recall,
        'coverage@50': coverage,
        'efi': efi,
        'gini_diff': gini_diff
    }

def ndcg_score(true_labels, pred_scores, k=10):
    """Calculate Normalized Discounted Cumulative Gain"""
    # Implementation using sklearn's ndcg_score
    from sklearn.metrics import ndcg_score
    return ndcg_score([true_labels], [pred_scores], k=k)

def coverage_score(exposures, total_items):
    """Calculate coverage@k"""
    return len(exposures) / total_items

def exposure_fairness(exposures):
    """Calculate Exposure Fairness Index (EFI)"""
    exposures = np.array(list(exposures.values()))
    n = len(exposures)
    if n == 0:
        return 0
    return 1 - np.sqrt(np.sum((exposures / exposures.sum() - 1/n)**2)) * np.sqrt(n/(n-1))

def gini_difference(exposures, popularity):
    """Calculate Gini coefficient difference"""
    rec_gini = gini(list(exposures.values()))
    true_gini = gini(list(popularity.values()))
    return rec_gini - true_gini

def gini(values):
    """Calculate Gini coefficient"""
    sorted_values = np.sort(values)
    n = len(sorted_values)
    index = np.arange(1, n+1)
    return (np.sum((2 * index - n - 1) * sorted_values)) / (n * np.sum(sorted_values))

def curriculum_weight(bias_score, t, total_t):
    """Curriculum learning weight scheduler"""
    # Implementation of equation (6)
    return 1 / (1 + np.exp(-t/total_t * bias_score))
